# -*- coding: utf-8 -*-
import configparser
import json
import random
import ssl
import time
import traceback

from paho.mqtt import client as mqtt_client

from conn.MyLogs import MyLogs
from conn.MyRedisFactory import RedisConfig

f = r'setting.ini'
config_file = configparser.RawConfigParser()
config_file.read(f, encoding='UTF8')
rds_cfg = RedisConfig(host=config_file['redis']['host'],
                      passwd=config_file['redis']['password'],
                      port=config_file['redis']['port'])
RedisConfig.get_pool(rds_cfg)
conn_flag = False
use_tsl = False
client_id = f'python-mqtt-pub-{random.randint(0, 1000)}'  # 客户端id不能重复
device_dict = {"device_id": "", "status": 0}


# ***
# 参考文献:
# https://blog.csdn.net/HELLOWORLD2424/article/details/127790938
class Subscriber(object):
    device_array = []
    device_id_array = []
    is_sub_run = False
    client = None
    subscriber_cfg = None
    mqtt_broker_cfg = None

    def __init__(self, cfg, subscriber, mqtt_broker, is_run):
        """
        MQTT消息订阅客户端
        :param cfg:
        """
        Subscriber.client = cfg
        # subscriber配置部分
        # self.subscriber_cfg = config_file["subscriber"]
        Subscriber.subscriber_cfg = subscriber
        # mqtt broker配置部分
        # self.mqtt_broker_cfg = config_file["mqtt_broker"]
        Subscriber.mqtt_broker_cfg = mqtt_broker
        # 连接mqtt代理服务器，并获取连接引用
        Subscriber.client = mqtt_client.Client(client_id)
        # 绑定回调函数 连接回调函数
        Subscriber.client.on_connect = self.connect_callback
        # 断开连接时候的回调函数
        Subscriber.client.on_disconnect = self.disconnect_callback
        # 收到消息的回调
        Subscriber.client.on_message = self.on_message_callback
        # 断开连接时候回调
        Subscriber.client.on_disconnect = self.disconnect_callback
        # 订阅消息处理
        Subscriber.client.on_subscribe = self.subscribe_callback
        Subscriber.is_sub_run = is_run

    def create_context(self):
        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
        context.load_cert_chain(
            certfile=self.subscriber_cfg["certfile"],
            keyfile=self.subscriber_cfg["keyfile"],
            password=self.subscriber_cfg["cert_pass"],
        )
        return context

    # '''连接回调函数'''
    @staticmethod
    def connect_callback(client, userdata, flags, rc):
        # rc == 0 为成功，否则就失败
        # assert rc == 0
        # 响应状态码为0表示连接成功
        if rc == 0:
            print("Connected to MQTT OK!")
            global conn_flag
            conn_flag = True
            MyLogs.logger.info("\nMQTT broker connected successfully,\n"
                               "address: {},\n"
                               "port: {}\n".format(client._host, client._port))
        else:
            print("Failed to connect, return code %d\n", rc)

    @staticmethod
    def disconnect_callback(client, userdata, rc):
        # rc == 0 为成功，否则就失败
        if rc == 0:
            MyLogs.logger.info("MQTT broker Conn Error")
        MyLogs.logger.info("\nDisconnect from MQTT broker,\n"
                           "address: {},\n"
                           "port: {}\n".format(client._host, client._port))

    @staticmethod
    def subscribe_callback(client, userdata, mid, granted_qos):
        MyLogs.logger.info("Subscribed successfully.\n")

    @staticmethod
    def on_message_callback(client, userdata, message):
        msg_bytes = message.payload
        msg_str = msg_bytes.decode()
        # 这里是信息回调的集中处理，需要甄别不同的topic，如果是设备心跳topic，需要判定数据，并上报设备在线异常。
        if message.topic == "my/vphonor/sub_data/heart":
            Subscriber.device_heart_info_2_rds(msg_str)
        else:
            RedisConfig.put_array_intimes(rds_cfg, f'topic:{message.topic}', msg_str, inTimes=60 * 60)
        MyLogs.logger.info("\nReceived message:\n"
                           "[topic]: {}\n"
                           "[content]: {}\n".format(message.topic, msg_str))

    def connect_mqtt_broker(self):
        """
        链接mqtt_broker服务器
        :return:
        """
        # 设置用户名和密码
        self.client.username_pw_set(username=self.mqtt_broker_cfg["username"],
                                    password=self.mqtt_broker_cfg["password"])
        if (use_tsl):
            # 使用context写法
            context = self.create_context()
            self.client.tls_set_context(context=context)
            # 不用context的写法
            # self.client.tls_set(ca_certs="cert/ca.crt", tls_version=ssl.PROTOCOL_TLSv1_2)
            self.client.tls_insecure_set(use_tsl)
        # 连接 IP port keepalive
        self.client.connect(self.mqtt_broker_cfg["address"],
                            int(self.mqtt_broker_cfg["port"]),
                            int(self.mqtt_broker_cfg["keepalive"]))
        while not conn_flag:
            time.sleep(0.1)
            self.client.loop()
            # self.client.loop_start()

    def connect_dev_heart(self):
        """
        开始监听mqtt消息
        :return:
        """
        # 连接broker
        self.connect_mqtt_broker()
        # 订阅的 topic
        self.client.subscribe(self.subscriber_cfg["heart"], qos=0)
        MyLogs.logger.info("\nStarting monitoring on broker for following topic: \n"
                           "[topic]: {}\n".format(self.subscriber_cfg["heart"]))
        time.sleep(5)
        self.client.loop_forever()

    def running(self):
        """
        开始监听mqtt消息
        :return:
        """
        if not self.is_sub_run:
            return
        # 连接broker
        self.connect_mqtt_broker()
        # 获取设备心跳
        self.connect_dev_heart()
        # # 订阅的 topic
        # self.client.subscribe(self.subscriber_cfg["topic"], qos=0)
        MyLogs.logger.info("\nStarting monitoring on broker for following topic: \n"
                           "[topic]: {}\n".format(self.subscriber_cfg["topic"]))
        time.sleep(10)
        self.client.loop_forever()

    @staticmethod
    def sub_dev_info(device_id):
        try:
            str_topic = Subscriber.subscriber_cfg["topic"]
            str_topic = str_topic.replace("{dName}", device_id)
            # 订阅的 topic
            Subscriber.client.subscribe(str_topic, qos=0)
        except Exception as e:
            MyLogs.logger.error(e)

    def publish(self):
        topic = "/python/mqtt"  # 消息主题
        '''发布消息'''
        while True:
            '''每隔4秒发布一次服务器信息'''
            time.sleep(4)
            msg = "{\"hello\":\"world\"}"
            result = self.client.publish(topic, msg)
            status = result[0]
            if status == 0:
                print(f"Send `{msg}` to topic `{topic}`")
            else:
                print(f"Failed to send message to topic {topic}")

    @staticmethod
    def device_heart_info_2_rds(msg_str):
        json_str = json.loads(msg_str)
        rds_key = json_str['device_id']  # 从设备端获取的设备ID
        device_dict['device_id'] = json_str['device_id']
        device_dict['status'] = json_str['status']
        device_dict['timestamp'] = current_time_millis()
        # 进行数据去重，循环历史数据队列
        for index_device in range(0, len(Subscriber.device_array)):
            try:
                _old_data = Subscriber.device_array[index_device][rds_key]
                _old_data['status'] = json_str['status']
                Subscriber.device_array[index_device] = {rds_key: _old_data}
            except KeyError as e:
                Subscriber.device_array.append({rds_key: device_dict})
                Subscriber.device_id_array.append(rds_key)
                print("Error:" + e)
        if len(Subscriber.device_array) == 0:
            Subscriber.device_array.append({rds_key: device_dict})
            Subscriber.device_id_array.append(rds_key)
        obj = RedisConfig.get_val(rds_cfg, key=f'device_h:{rds_key}', json_flg=True)
        if obj is None:
            RedisConfig.push_val_intimes(rds_cfg, key=f'device_h:{rds_key}', val=device_dict, json_flg=True,
                                         inTimes=60 * 60)
        else:
            RedisConfig.set_val_intimes(rds_cfg, key=f'device_h:{rds_key}', val=device_dict, json_flg=True,
                                        inTimes=60 * 60)
        # print(json.dumps(Subscriber.device_array, ensure_ascii=False))


def current_time_millis():
    return str(int(round(time.time() * 1000)))


if __name__ == '__main__':
    sub = Subscriber(cfg=mqtt_client)
    try:
        sub.running()
    except Exception as e:
        # 服务启动失败
        MyLogs.logger.error(traceback.format_exc())
        raise e
